package org.school.streaming.net.server;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.StringTokenizer;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;

import org.school.streaming.io.VideoStream;
import org.school.streaming.net.RequestType;
import org.school.streaming.net.State;
import org.school.streaming.net.client.packets.RTPPacket;
import org.school.streaming.net.server.discovery.DiscoveryHandler;

/**
 * @authors Jeffrey Goudzwaard, Davin Terrizzi
 * @version 1.0
 * @date 18-4-2013
 */

/**
 * Class representing streaming server and its GUI
 * 
 * @author Davin
 * 
 */
public class Server extends JFrame implements ActionListener {

	private static final long serialVersionUID = -8825263093352224425L;

	private static final String NAME = "Server";
	private static final String CRLF = "\r\n";

	private static Server INSTANCE;

	// RTP type for MJPEG
	private static int MJPEG_TYPE = 26;

	private static int FRAME_PERIOD = 100;

	// Hardcoded amoun of video frames
	private static int VIDEO_LENGTH = 500;

	private static int id = 123456;

	// Streamreader and streamwriter for network
	private static BufferedReader READER;
	private static BufferedWriter WRITER;

	private static String videoFileName;

	private DatagramSocket ds;
	private Socket socket;
	private DatagramPacket dgp;
	private InetAddress clientAddr;

	private int destPort = 0;

	// Current image frame in GUI
	private int imageNr = 0;

	// Current RTP sequence/frame
	private int seqNr = 0;

	private VideoStream vs;

	private JLabel lbl;
	private Timer timer;

	private byte[] buffer;

	// Current server state
	private State state;

	/**
	 * Gets current server instance for calling classes
	 * 
	 * @return Server instance
	 */
	public static Server getServer() {
		if (INSTANCE == null) {
			INSTANCE = new Server();
		}

		return INSTANCE;
	}

	/**
	 * Constructor Initializes discovery and streaming server
	 */
	private Server() {

		try {
			initDiscovery();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}

		init();
	}

	/**
	 * Initializes discovery Registers server and its available video files in
	 * database. Only calls DiscoveryHandler constructor
	 * 
	 * @throws MalformedURLException
	 * @throws UnknownHostException
	 */
	private void initDiscovery() throws MalformedURLException, UnknownHostException {
		DiscoveryHandler discHandler = new DiscoveryHandler();
	}

	/**
	 * Initializes server
	 */
	private void init() {

		// Set JFrame title
		setTitle(NAME);

		// Initialize Timer based on movie length
		timer = new Timer(FRAME_PERIOD, this);
		timer.setInitialDelay(0);
		timer.setCoalesce(true);

		// Memory allocation for buffer
		buffer = new byte[15000];

		// Make JLabel for showing current sent frame no.
		lbl = new JLabel("Send frame #		", JLabel.CENTER);
		getContentPane().add(lbl, BorderLayout.CENTER);

		// If user closes window make sure the Timer and server GUI close nicely
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				timer.stop();
				System.exit(0);
			}
		});
	}

	/**
	 * 
	 * 
	 */
	@Override
	public void actionPerformed(ActionEvent ae) {

		// Increment GUI frame no. until end is reached
		if (imageNr < VIDEO_LENGTH) {
			imageNr++;

			try {
				int imageLength = vs.getNextFrame(buffer);

				// Create new RTP packet based on frame information
				RTPPacket packet = new RTPPacket(MJPEG_TYPE, imageNr, imageNr * FRAME_PERIOD, buffer, imageLength);

				// Get length of packet to send, put into byte array
				int packetLength = packet.getLength();
				byte[] packetBits = new byte[packetLength];

				// Fill packet payload with RTP packet data from byte array
				packet.getPacket(packetBits);

				// Create new UDP datagram and fill it with RTP + network
				// information
				dgp = new DatagramPacket(packetBits, packetLength, clientAddr, destPort);

				// Send UDP datagram
				ds.send(dgp);

				// Log packet header to standard output
				packet.printHeader();

				// Update GUI label to show frame is successfully sent
				lbl.setText("Send frame #" + imageNr);
			} catch (Exception e) {
				System.err.println(e);
				System.exit(1);
			}
		} else {
			timer.stop();
		}
	}

	// Initializes GUI
	public void showFrame() {
		pack();
		setVisible(true);
	}

	/**
	 * Parses request from streaming client
	 * @return Correct request type made by client
	 */
	public RequestType parseRequest() {
		RequestType requestType = RequestType.NULL;

		try {
			
			// Read line from BufferedReader
			String requestLine = READER.readLine();
			System.out.println(requestLine);

			// Filter out request message
			StringTokenizer tokens = new StringTokenizer(requestLine);
			String requestTypeString = tokens.nextToken();
			requestType = RequestType.valueOf(requestTypeString);
			System.out.println("RequestType: " + requestType.toString());

			// Set video file for server if client request is SETUP
			if (requestType == RequestType.SETUP) {
				videoFileName = tokens.nextToken();
			}

			// Get request frame and set server frame accordingly
			String seqNumLine = READER.readLine();
			System.out.println(seqNumLine);
			tokens = new StringTokenizer(seqNumLine);
			tokens.nextToken();
			seqNr = Integer.parseInt(tokens.nextToken());

			String lastLine = READER.readLine();
			System.out.println(lastLine);

			// Get destination port and set accordingly in srver if request type is SETUP
			if (requestType == RequestType.SETUP) {
				tokens = new StringTokenizer(lastLine);
				for (int i = 0; i < 3; i++) {
					tokens.nextToken();
				}
				destPort = Integer.parseInt(tokens.nextToken());
			}
		} catch (Exception e) {
			System.err.println("parseRequest error: " + e);
			System.exit(1);
		}

		return requestType;
	}

	/**
	 * Send server response (RSTP headers) to BufferedWriter
	 */
	public void sendResponse() {
		try {
			WRITER.write("RTSP/1.0 200 OK" + CRLF);
			WRITER.write("CSeq: " + seqNr + CRLF);
			WRITER.write("Session: " + id + CRLF);
		} catch (Exception e) {
			System.err.println(e);
			System.exit(1);
		} finally {
			try {
				WRITER.flush();
			} catch (IOException e) {
				System.err.println(e);
			}
		}
	}

	public void setServerState(State state) {
		this.state = state;
	}

	public State getServerState() {
		return state;
	}

	public DatagramSocket getDatagramSocket() {
		return ds;
	}

	public void setDatagramSocket(DatagramSocket ds) {
		this.ds = ds;
	}

	public Socket getSocket() {
		return socket;
	}

	public void setSocket(Socket socket) {
		this.socket = socket;
	}

	public void setClientIPAddress(InetAddress clientIPAddress) {
		this.clientAddr = clientIPAddress;
	}

	public void setBufferedReader(BufferedReader reader) {
		READER = reader;
	}

	public BufferedReader getReader() {
		return READER;
	}

	public void setBufferedWriter(BufferedWriter writer) {
		WRITER = writer;
	}

	public BufferedWriter getWriter() {
		return WRITER;
	}

	public void setVideo(VideoStream vs) {
		this.vs = vs;
	}

	public String getVideoFileName() {
		return videoFileName;
	}

	public Timer getTimer() {
		return timer;
	}
}